package com.xu.cloud.common.util;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Objects;

/**
 * 对称加密工具类
 * <p>
 * 由于安全源策略, jdk默认只能使用AES-128加密算法
 * 从 Java 1.8.0_151 和 1.8.0_152 开始, 有一种新的、更简单的方法来为 JVM 启用无限强度管辖策略, 如果不启用此功能, 则无法使用 AES-256.
 * 从此版本开始, 不再需要从 Oracle 网站下载JCE策略文件并安装它. 可以使用以下单行代码直接在应用程序中设置无限策略:
 * <blockquote><pre>
 *     Security.setProperty("crypto.policy", "unlimited");
 * </pre></blockquote>
 * Java 1.8_161及以上的版本已经默认采用无限制策源
 *
 * @author xuguan
 * @since 2024/6/30
 */
public class SymmetricEncryptUtil {

    static {
        // 启用无限强度管辖策略
        Security.setProperty("crypto.policy", "unlimited");
        final Provider provider = Providers.createBouncyCastleProvider();
        if (Objects.nonNull(provider)) {
            Security.addProvider(provider);
        }
    }

    /**
     * 生成指定长度密钥
     *
     * @param length    生成指定长度的密钥(AES-128, AES-192, AES-256, SM4-128)
     * @param algorithm 加解密算法
     * @return 密钥
     */
    public static SecretKey generateKey(int length, String algorithm) {
        KeyGenerator keyGen;
        try {
            keyGen = KeyGenerator.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("no such algorithm", e);
        }
        keyGen.init(length);
        return keyGen.generateKey();
    }

    /**
     * 通过指定字节数组获取密钥
     *
     * @param keyBytes  字节数组
     * @param algorithm 加解密算法
     * @return 密钥
     */
    public static SecretKey getKeyFromBytes(byte[] keyBytes, String algorithm) {
        return new SecretKeySpec(keyBytes, algorithm);
    }

    public static String encryptHex(String data, SecretKey key, String algorithm) {
        return CodecUtil.hexString(encrypt(data.getBytes(StandardCharsets.UTF_8), key, algorithm));
    }

    public static String decryptHex(String encryptedHex, SecretKey key, String algorithm) {
        final byte[] bytes = CodecUtil.byteArray(encryptedHex);
        return new String(decrypt(bytes, key, algorithm), StandardCharsets.UTF_8);
    }

    public static String encryptBase64(String data, SecretKey key, String algorithm) {
        return CodecUtil.base64EncodeString(encrypt(data.getBytes(StandardCharsets.UTF_8), key, algorithm));
    }

    public static String decryptBase64(String encryptedBase64, SecretKey key, String algorithm) {
        final byte[] bytes = CodecUtil.base64Decode(encryptedBase64);
        return new String(decrypt(bytes, key, algorithm), StandardCharsets.UTF_8);
    }

    /**
     * 加密
     *
     * @param data      原始数据
     * @param key       密钥
     * @param algorithm 加解密算法
     * @return 加密后的数据
     */
    public static byte[] encrypt(byte[] data, SecretKey key, String algorithm) {
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("no such algorithm", e);
        } catch (NoSuchPaddingException e) {
            throw new IllegalArgumentException("no such padding", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("invalid key", e);
        } catch (IllegalBlockSizeException e) {
            throw new IllegalArgumentException("illegal block size", e);
        } catch (BadPaddingException e) {
            throw new IllegalArgumentException("bad padding", e);
        }
    }

    /**
     * 解密
     *
     * @param encryptedData 加密后的数据
     * @param key           密钥
     * @param algorithm     加解密算法
     * @return 原始数据
     */
    public static byte[] decrypt(byte[] encryptedData, SecretKey key, String algorithm) {
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return cipher.doFinal(encryptedData);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("no such algorithm", e);
        } catch (NoSuchPaddingException e) {
            throw new IllegalArgumentException("no such padding", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("invalid key", e);
        } catch (IllegalBlockSizeException e) {
            throw new IllegalArgumentException("illegal block size", e);
        } catch (BadPaddingException e) {
            throw new IllegalArgumentException("bad padding", e);
        }
    }
}
